package core.proto.rightwindow;
import static app.constants.CollabVizConstants.*;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.beans.PropertyVetoException;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map.Entry;
import java.util.Properties;

import javax.swing.BorderFactory;
import javax.swing.JDesktopPane;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.TransferHandler;
import javax.swing.event.InternalFrameEvent;
import javax.swing.event.InternalFrameListener;

import org.apache.commons.io.IOUtils;

import core.proto.InfoViz;
import core.proto.Util;
import core.proto.annotation.Annotation;

public class BoardPane extends JDesktopPane implements MouseMotionListener, MouseListener,ActionListener, Runnable, ComponentListener, FocusListener, InternalFrameListener, KeyListener, DropTargetListener {

	public static final String INVALID_GROUP = "";

	private static BoardPane instance = null; // singleton 

	private HttpChatPanel chatPanel;
	private Point delta; // For handling selecting multiple frames. 

	private Rectangle mouseRect;
	private static List<PostIt> allPostIts;

	private static final String REGROUP = "regroup";
	private static final int WIDE = 800;
	private static final int HIGH = 600;

	private boolean selecting = false;
	private Point mousePt;
	private JPopupMenu popup;

	private PopupPanel popupEditor;

	private Hashtable<String, List<PostIt>> postItGroups;

	private Thread postItUpdateProcessor; 
	private LinkedList<String> postItUpdates;
	private Hashtable<String, PostIt> foreignPostIts; // PostIt from other users. 

	private PostIt postItInFocus = null;

	private static final int dx = 15; 
	private static final int dy = 20;

	public final TransferHandler transferHandler; 

	// group is defined by screen position so this is just for convenience of storing groups. 
	private long groupCounter = Long.MIN_VALUE;	

	private JLabel plusArea;

	//ERIC
	private static ArrayList<postItPair> singleLinkMap = new ArrayList<postItPair>();
	private static PostIt singlePostIt = null;
	private static ArrayList<PostIt> groupLinkList = new ArrayList<PostIt>();
	private static HashMap<ArrayList<PostIt>, ArrayList<PostIt>> groupLinkMap = new HashMap<ArrayList<PostIt>, ArrayList<PostIt>>();
	private static boolean paintLinks = false;
	private static double currMeX = 0.0;
	private static double currMeY = 0.0;
	private static postItPair toDelete;


	private static Object lock = new Object ();

	public static BoardPane getInstance () {
		if (instance == null) {
			synchronized(lock) {
				if (instance == null){
					instance = new BoardPane();
				}
			}
		}
		return instance;
	}

	private BoardPane() {
		transferHandler = new PostItTransferHandler();
		this.setTransferHandler(transferHandler);
		this.setupDropTarget();

		delta = new Point();

		foreignPostIts = new Hashtable<String, PostIt> ();

		mouseRect = new Rectangle();

		postItGroups = new Hashtable<String, List<PostIt>>();

		popup = new JPopupMenu();
		JMenuItem menuItem = new JMenuItem(REGROUP);
		menuItem.addActionListener(this);
		popup.add(menuItem);

		allPostIts = new ArrayList<PostIt>();
		//		selected = new ArrayList<PostIt>();
		mousePt = new Point(WIDE / 2, HIGH / 2);

		setDragMode(OUTLINE_DRAG_MODE);
		this.setSize(WIDE,HIGH);

		this.setLayout(null);
		this.addMouseMotionListener(this);
		this.addMouseListener(this);
		this.addKeyListener(this);

		popupEditor = new PopupPanel(this);
		this.add(popupEditor, JLayeredPane.PALETTE_LAYER);
		popupEditor.setVisible(false);

		setUpPlusArea ();
		setUpChatPanel ();

		postItUpdateProcessor = this.createPostItUpdateThread();
		postItUpdates = new LinkedList<String>();
		postItUpdateProcessor.start();
		new Thread(this).start();
	}

	private void setupDropTarget () {
		DropTargetListener listener = new DropTargetListener() {

			@Override
			public void dragEnter(DropTargetDragEvent dtde) {
				// TODO Auto-generated method stub
				Util.p("Drag enter");
			}

			@Override
			public void dragExit(DropTargetEvent dte) {
				// TODO Auto-generated method stub
				Util.p("Drag exit");
			}

			@Override
			public void dragOver(DropTargetDragEvent dtde) {
				// TODO Auto-generated method stub
				Util.p("Drag over");
			}

			@Override
			public void drop(DropTargetDropEvent dtde) {
				// TODO Auto-generated method stub
				Util.p("Dropping poop");
			}

			@Override
			public void dropActionChanged(DropTargetDragEvent dtde) {
				// TODO Auto-generated method stub
				Util.p("Drop action delta");
			}

		};

		DropTarget target = new DropTarget(this, DnDConstants.ACTION_MOVE, listener, true, null);
		this.setDropTarget(target);
	}

	private void setUpPlusArea () {
		this.plusArea = new JLabel ();
		plusArea.setBounds(0,0,PostIt.SIDE_LENGTH, PostIt.SIDE_LENGTH);
		plusArea.setOpaque(false);	// transparent
		plusArea.setBorder(BorderFactory.createLineBorder(Color.black));

		plusArea.setText("+");
		plusArea.setHorizontalAlignment(JLabel.CENTER);
		plusArea.setVerticalAlignment(JLabel.CENTER);

		this.add(plusArea, JLayeredPane.DEFAULT_LAYER);
	}

	private void setUpChatPanel () {
		chatPanel = new HttpChatPanel();
		int chatY = (int) (this.getHeight() * (1 - CHAT_HEIGHT_PERCENT)); 
		int chatX = 0;
		int chatW = (int) (this.getHeight() * CHAT_HEIGHT_PERCENT);
		int chatH = (int) (this.getHeight() * (1 - CHAT_HEIGHT_PERCENT));
		chatPanel.setBounds(chatX, chatY, chatW, chatH);
		this.add(chatPanel, JLayeredPane.DEFAULT_LAYER);
	}

	// Creates the thread that would process the post it updates 
	// that we pulled from the server. 
	private Thread createPostItUpdateThread () {
		return new Thread() {
			LinkedList<String> toProcess = new LinkedList<String>();

			public void run () {
				while (true) {
					try {
						// no need to put in a while loop
						// because this is the only thread waiting on it.
						// The synchronized block needed because this thread is not the owner 
						// of postItUpdates and security manager will bark otherwise and give exception. 
						synchronized(postItUpdates){
							postItUpdates.wait();
							toProcess.addAll(postItUpdates);
							postItUpdates.clear();
						}
					} catch (InterruptedException e) {
						e.printStackTrace();
					}

					// Run in the event processin thread's context to avoid having to worry about 
					// concurrency issues with the white board's data structures. 
					SwingUtilities.invokeLater(new Runnable(){

						private void handleForeignPostIt (Properties props, String networkString) {
							String key = PostIt.getUniqueKey(props);
							String content = PostIt.getMessage(props);

							if (! foreignPostIts.containsKey(key)){
								if (Annotation.shouldDeleteAnnotationWith(content)){
									return;
								}
								// Add new post it!
								Util.p("Debug: Adding postit from network");
								PostIt pi = new PostIt(networkString);
								pi.setText(content);
								// addPostIt method takes care of cascading stuff. 
								addPostIt(pi, PostIt.getLocation(props));
								foreignPostIts.put(key, pi);
							} else {
								// look up the post it using its key
								PostIt pi = foreignPostIts.get(key);
								if (Annotation.shouldDeleteAnnotationWith(content)) {
									// Delete
									Util.p("Debug: deleting postit from network");
									removePostIt(pi);
									foreignPostIts.remove(key);
								} else {
									Util.p("Debug: updating postit from network");
									pi.setText(content);
									// ComponentMoved gets triggered each time we change 
									// the location. In this case we don't want it to 
									// broadcast the change of location because the 
									// change didn't originate locally. 
									pi.setJustChangedLocOvetNetwork(true);
									pi.setLocation(PostIt.getLocation(props));
								}
							}
						}

						private void handleLocalPostIt (Properties props) {
							long sid = PostIt.getSid(props);
							for(PostIt p : allPostIts){
								if (p.getSessionId() == sid){
									// ComponentMoved gets triggered each time we change 
									// the location. In this case we don't want it to 
									// broadcast the change of location because the 
									// change didn't originate locally. 
									p.setJustChangedLocOvetNetwork(true);
									p.setLocation(PostIt.getLocation(props));
								}
							}
						}

						@Override
						/**
						 * PostIt update processor thread. 
						 */
						public void run() {
							for(String networkString : toProcess) {

								// At this point there are two kinds of updates. 
								// First kind: owner updates text and pushes it out. 
								// Second kind: either user moves the post it around and we need to mirror the screen positions. 

								// First extract the properties object. 
								Properties props = PostIt.toProperties(networkString);
								if (props == null) // malformed input. 
									continue;

								// Ignore messages broadcasted back to self.  
								int tok = PostIt.getSenderToken(props);
								if (tok == InfoViz.userToken){
									continue;
								}

								int owner = PostIt.getOwnerToken(props);
								if (owner == InfoViz.userToken){
									this.handleLocalPostIt(props);
								} else {
									handleForeignPostIt(props, networkString);		
								}
							}
							toProcess.clear();
						}
					});
				}
			}
		};
	}

	private long getNextGroup () {
		return groupCounter++;
	}

	public static List<PostIt> getNodes() {
		return allPostIts;
	}

	public static void setNodes(List<PostIt> nodes) {
		BoardPane.allPostIts = nodes;
	}

	public void removePostIt (PostIt pi) {
		// in critical section because this can be updated asynchronously 
		// when the remote user's postIt is deleted. 
		allPostIts.remove(pi);
		removeFromGroup(pi);
		this.remove(pi);
		this.repaint();
		
		for(postItPair pair : singleLinkMap){
			if(pair.contains(pi)){
				singleLinkMap.remove(pair);
			}
		}
	}

	/**
	 * Side effect: remove pi from the group it belongs to. 
	 * Also delete the group if no more post its belong to that group. 
	 * @param pi
	 */
	private void removeFromGroup (PostIt pi) {
		if (postItGroups.containsKey(pi.getGroup())){
			postItGroups.get(pi.getGroup()).remove(pi);
			if (postItGroups.get(pi.getGroup()).isEmpty()) {
				postItGroups.remove(pi.getGroup());
			}
		}
	}

	private void cascadePostIts (final List<PostIt> theSelected, double pivotX, double pivotY, boolean sort, boolean withAnimation) {
		if (sort)
			Collections.sort(theSelected);

		final int ANIMATION_FREQ = 200;

		if (withAnimation) {
			ActionListener listener = new ActionListener() {
				public Timer timer; 
				int curIndex = 0;
				{
					timer = new Timer(ANIMATION_FREQ, this);
					timer.start();
				}

				// Animation event.
				@Override
				public void actionPerformed(ActionEvent arg0) {
					++curIndex;
					if (curIndex < theSelected.size())
						timer.restart();
				}

			};
		}

		for (int i = 0; i < theSelected.size(); i++) {
			PostIt current = theSelected.get(i);
			current.setLocation((int) pivotX, (int) pivotY);
			pivotX = pivotX - dx;
			pivotY = pivotY- dy;
			try {
				current.setSelected(false);
			} catch (PropertyVetoException e) {
			}
		}
		for(int i=theSelected.size()-1; i>-1; i--) {
			PostIt current = theSelected.get(i);
			current.toFront();
		}
	}



	//todowei fix cascading to avoid ever overlapping the rectangle 
	// containing the chat area and the plus area. 

	/**
	 * Cascade in the order given. 
	 * @param theSelected
	 */
	private void cascadePostIts (List<PostIt> theSelected) {
		// Sort it from most recent to least recently added.
		Collections.sort(theSelected);
		if (theSelected.size() > 1) {
			// Coordinates of the most recently created PostIt. 
			int pivotX = theSelected.get(0).getX();
			int pivotY = theSelected.get(0).getY();

			if (pivotY - dy * (theSelected.size() - 1) < 0) {
				pivotY = dy * (theSelected.size()  - 1);
			}

			if (pivotX - dx * (theSelected.size() - 1) < 0) {
				pivotX = dx * (theSelected.size() - 1);
			}

			cascadePostIts(theSelected, pivotX, pivotY, false, false);		
		}

	}

	/**
	 * 
	 * @return Whether we had a post it in focus when this was called.   
	 */
	private boolean stopEditing () {
		if (this.postItInFocus != null) {
			if (popupEditor.isVisible()) {
				// the setVisible method would do the side effects of syncing with 
				// left window and over network. 
				popupEditor.setVisible(false);
			} else {
				postItInFocus.makeUnEditable();
				LeftRightCommunicator.postItUpdated(postItInFocus);
			}
			try {
				this.postItInFocus.setSelected(false);
			} catch (PropertyVetoException e) {
			}
			this.postItInFocus = null;
			return true;
		}
		return false;
	}

	public void actionPerformed(ActionEvent e) {
		String cmd = e.getActionCommand();

		if (REGROUP.equals(cmd)) {
			stopEditing();

			List<PostIt> selected = new LinkedList<PostIt>();
			PostIt.getSelect(allPostIts, selected);
			if (selected.size() > 0) {				
				this.mergeIntoOneGroup(selected);
				for(PostIt pi:allPostIts) {
					// Restore the color to the normal ones.
					pi.restoreNormalColor ();
				}
			} else {
				// Nothing selected, cascade every group. 
				for(Entry<String,List<PostIt>> entry : postItGroups.entrySet()) {
					List<PostIt> postIts = entry.getValue();
					this.cascadePostIts(postIts);
				}
			}
		}
		this.repaint();
	}

	/**
	 * Add the PostIt to the white board. 
	 * All external users must call this method to add a post it.  
	 * @param pi
	 */
	public void addPostItFromLeftWindow(PostIt pi) {
		this.addPostIt(pi, this.plusArea.getLocation());
	}

	private void addPostIt(PostIt pi, Point location) {
		pi.parent = this;
		pi.setLocation(location);
		pi.setOrigin(location);
		allPostIts.add(pi);
		determineAndAddToGroup (pi, location);
		pi.setVisible(true);
		this.add(pi, JLayeredPane.DEFAULT_LAYER);
		pi.addComponentListener(this);
		pi.addFocusListener(this);
		pi.addInternalFrameListener(this);
		repaint();
	}



	@Override
	public void mouseClicked(MouseEvent me) {
		// If we were editing something then stop. 
		if (stopEditing()) {
			return;
		}
		if(me.getClickCount() == 1) {
			final PostIt pi = new PostIt(InfoViz.userToken, false);
			pi.setBounds(me.getX(), me.getY(), pi.getWidth(), pi.getHeight());
			if (this.overlapsWithForbiddenAreas(pi)) {
				// Ignore mouse clicks that ends up creating post it that 
				// overlaps with plus area or the chat area. 
				return; 
			}
			addPostIt(pi, me.getPoint());
			try {
				pi.setSelected(true);
			} catch (PropertyVetoException e) {
			}
			pi.toFront();
		}
		Point p = me.getLocationOnScreen();
		System.out.println("LOCATION " + p.getX() + " " + p.getY());
	
		
		
		BoardPane.getInstance().repaint();
	}

	@Override
	public void mouseEntered(MouseEvent arg0) {

	}

	@Override
	public void mouseExited(MouseEvent arg0) {

	}

	@Override
	public void mousePressed(MouseEvent e) {

		mousePt = e.getPoint();
		if (e.isShiftDown()) {
			PostIt.selectToggle(allPostIts, mousePt);
		} else if (e.isPopupTrigger() && postItGroups.entrySet().size() > 1) {
			showPopup(e);
		}else if(e.getModifiers() == InputEvent.BUTTON3_MASK){
			currMeX= e.getX();
			currMeY = e.getY();
		} 
		else {
			selecting = true;
		}
		e.getComponent().repaint();		
		
		
		
		BoardPane.getInstance().repaint();
	}

	@Override
	public void mouseReleased(MouseEvent e) {
		selecting = false;
		mouseRect.setBounds(0, 0, 0, 0);
		if (e.isPopupTrigger() && postItGroups.entrySet().size() > 1) {
			showPopup(e);
		}
		e.getComponent().repaint();		
	}

	

	private void showPopup(MouseEvent e) {
		popup.show(e.getComponent(), e.getX(), e.getY());
	}

	@Override
	/**
	 * Thread that polls the server for PostIt updates. 
	 */
	public void run() {
		while (true) {
			InputStream in;
			try {
				// Blocking call. 
				in = WebUtil.pullPostItUpdates();

				StringWriter writer = new StringWriter();
				IOUtils.copy(in, writer); 
				String message = writer.toString();

				// Just delegate the work to the processor thread. 
				synchronized(postItUpdates) {
					postItUpdates.add(message);
					postItUpdates.notifyAll();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	@Override
	public void componentHidden(ComponentEvent e) {
	}

	private boolean overlapsWithForbiddenAreas (PostIt pi) {
		return pi.getBounds().intersects(plusArea.getBounds()) || pi.getBounds().intersects(chatPanel.getBounds());
	}

	public int getChatAreaWidth () {
		return chatPanel.getWidth();
	}

	@Override
	/**
	 * For listen to movement (dragging) of PostIt objects. Then regroup things accordingly. 
	 */
	public void componentMoved(ComponentEvent e) {
		if (e.getSource() instanceof PostIt){

			PostIt pi = (PostIt) e.getSource();
			boolean shouldNotifyOthers = true;

			if (this.overlapsWithForbiddenAreas(pi)) {
				if (pi.isNewlyAddedFromLeftWindow()) {
					// Bounce pi back to plus area  
					pi.setLocation(this.plusArea.getLocation());
				} else {
					// Bounce back to the last location. 
					pi.restoreToLastLocation();
				}
				shouldNotifyOthers = false;
			} else if (pi.isNewlyAddedFromLeftWindow()) {
				pi.markNotNewlyAddedFromLeftWindow();
			}

			List<PostIt> overlaps = this.findOverlaps(pi);
			Util.p("# overlaps = " + overlaps.size());
			this.mergeIntoOneGroup(overlaps);

			if (shouldNotifyOthers && ! pi.isJustChangedLocOvetNetwork()) {
				LeftRightCommunicator.postItUpdated(pi);
			}

			pi.setJustChangedLocOvetNetwork(false);
			
			
			//ERIC NEW GROUP
			ArrayList<ArrayList<PostIt>> groupsToCheck = new ArrayList<ArrayList<PostIt>>();
			for(ArrayList<PostIt> group: groupLinkMap.keySet()){
				
				if(group.contains(pi)){
					groupsToCheck.add(group);
				}
				else if(groupLinkMap.get(group).contains(pi)){
					groupsToCheck.add(groupLinkMap.get(group));
				}
			}
			for(ArrayList<PostIt> group: groupsToCheck){
				boolean overlap = false;
				for(PostIt p : group){
					if(overlaps(p, pi)){
						overlap = true;
					}
				}
				//if not over anything in the group, maintain 
//				if(!overlap){
//					ArrayList<PostIt> out = new ArrayList<PostIt>();
//					if(groupLinkMap.containsKey(group)){
//						out.add(pi);
//						groupLinkMap.put(out, groupLinkMap.get(group));
//					}
//					if(groupLinkMap.containsValue(group)){
//						out.add(pi);
//						for(ArrayList<PostIt> key : groupLinkMap.keySet()){
//							if(groupLinkMap.get(key) == group){
//								groupLinkMap.put(key, out);
//							}
//						}	
//					}
//					group.remove(pi);
//				}
//				
			}
			
			
			this.repaint();
		}
		
	}

	private List<PostIt> findOverlaps (PostIt pi) {
		List<PostIt> overlaps = new LinkedList<PostIt>();
		for (PostIt postIt : allPostIts) {
			if (overlaps(postIt, pi)) {
				overlaps.add(postIt);
			}
		}
		overlaps.add(pi);
		return overlaps;
	}

	private void mergeIntoOneGroup (List<PostIt> overlaps) {
		boolean sameGroup = true;
		boolean invalidGroupSeen = false;
		String lastGroup = null;
		for (PostIt postIt : overlaps) {
			invalidGroupSeen = invalidGroupSeen || postIt.getGroup().equals(INVALID_GROUP);
			if (lastGroup == null){
				lastGroup = postIt.getGroup();
				continue;
			}
			sameGroup = sameGroup && lastGroup.equals(postIt.getGroup());
			if (! sameGroup)
				break;
			lastGroup = postIt.getGroup();
		}
		if (sameGroup && ! invalidGroupSeen) {
			// check if there's only 1 post it. if so change group. 
			if (overlaps.size() == 1){
				mergeHelper(overlaps);
			}
			this.cascadePostIts(overlaps);
		} else {
			mergeHelper(overlaps);
		}
		this.cascadePostIts(overlaps);
	}

	private void mergeHelper (List<PostIt> overlaps) {
		String nextGroup = Long.toString(this.getNextGroup());
		for (PostIt postIt : overlaps) {
			this.removeFromGroup(postIt);
			postIt.setGroup(nextGroup);
		}
		postItGroups.put(nextGroup, overlaps);
	}

	/**
	 * Finds all the overlapping PostIt and group them together in a new group. 
	 * If pi belongs to some current group it is removed.
	 * @param pi
	 * @param group
	 */
	private void determineAndAddToGroup (PostIt pi, Point location) {
		List<PostIt> overlaps = this.findOverlaps(pi);
		this.mergeIntoOneGroup(overlaps);
	}

	private boolean overlaps(PostIt postIt, PostIt pi) {
		return pi.getBounds().intersects(postIt.getBounds()) && postIt != pi;
	}

	@Override
	public void componentResized(ComponentEvent e) {
	}

	@Override
	public void componentShown(ComponentEvent e) {
	}

	@Override
	public void focusGained(FocusEvent fe) {
	}

	@Override
	public void focusLost(FocusEvent fe) {
	}

	@Override
	public void internalFrameActivated(InternalFrameEvent fe) {
		PostIt pi = (PostIt) fe.getSource();
		stopEditing();

		postItInFocus = pi;		

		if (postItGroups.containsKey(pi.getGroup()) && postItGroups.get(pi.getGroup()).size() > 1) {
			// show popup editor because this post it's group has more than 1 element.
			List<PostIt> list = postItGroups.get(pi.getGroup());
			this.popupEditor.addPostIts(pi, list);
			this.popupEditor.setVisible(true);
		} else {
			// clicked on individual post it. make it editable.
			pi.makeEditable ();
		}
	}



	@Override
	public void internalFrameClosed(InternalFrameEvent arg0) {

	}

	@Override
	public void internalFrameClosing(InternalFrameEvent arg0) {

	}

	@Override
	public void internalFrameDeactivated(InternalFrameEvent fe) {
	}


	@Override
	public void internalFrameDeiconified(InternalFrameEvent arg0) {

	}

	@Override
	public void internalFrameIconified(InternalFrameEvent arg0) {

	}

	@Override
	public void internalFrameOpened(InternalFrameEvent arg0) {
	}

	@Override
	public void mouseDragged(MouseEvent e) {
		if (selecting) {
			mouseRect.setBounds(
					Math.min(mousePt.x, e.getX()),
					Math.min(mousePt.y, e.getY()),
					Math.abs(mousePt.x - e.getX()),
					Math.abs(mousePt.y - e.getY()));
			PostIt.selectRect(allPostIts, mouseRect);

		} else {
			delta.setLocation(
					e.getX() - mousePt.x,
					e.getY() - mousePt.y);
			mousePt = e.getPoint();
		}
		e.getComponent().repaint();

	}

	@Override
	public void mouseMoved(MouseEvent arg0) {
	}	

	private PostIt getPostItInPlusArea () {
		for(PostIt pi : allPostIts) {
			if (pi.getBounds().intersects(plusArea.getBounds()))
				return pi;
		}
		return null;
	}

	public boolean canCreateLocalPostIt () {
		return getPostItInPlusArea () == null;
	}

	public void keyTyped(KeyEvent e) {
		//System.out.println(" " + e.getKeyCode());
		/*if(e.getKeyCode() == KeyEvent.VK_DELETE){
			System.out.println("DELETE KEY PRESSED");
		}*/
		System.out.println("EXECUTE1");
	}

	public void keyPressed(KeyEvent e) {
		System.out.println("EXECUTE1");
		
	}

	public void keyReleased(KeyEvent e) {
		System.out.println("EXECUTE1");
	}
	
	@Override
	public void dragEnter(DropTargetDragEvent dtde) {
		// TODO Auto-generated method stub

	}

	@Override
	public void dragExit(DropTargetEvent dte) {
		// TODO Auto-generated method stub

	}

	@Override
	public void dragOver(DropTargetDragEvent dtde) {
		// TODO Auto-generated method stub

	}

	@Override
	public void drop(DropTargetDropEvent dtde) {
		// TODO Auto-generated method stub

	}

	@Override
	public void dropActionChanged(DropTargetDragEvent dtde) {
		// TODO Auto-generated method stub

	}	

	/*@Override
	public void paintComponent(Graphics g) {
		g.setColor(Color.LIGHT_GRAY);
		g.fillRect(0, 0, getWidth(), getHeight());
		if (selecting) {
			g.setColor(Color.darkGray);
			g.drawRect(mouseRect.x, mouseRect.y,
					mouseRect.width, mouseRect.height);
		}
	}*/

	//ERIC
	@Override
	public void paintComponent(Graphics g) {
		g.setColor(Color.LIGHT_GRAY);
		g.fillRect(0, 0, getWidth(), getHeight());
		
		
		
		if (selecting) {
			g.setColor(Color.darkGray);

			g.drawRect(mouseRect.x, mouseRect.y,
					mouseRect.width, mouseRect.height);

		}

		g.setColor(Color.darkGray);

		//Regular stroke
		BasicStroke s = new BasicStroke(2.0f,
				BasicStroke.CAP_ROUND,
				BasicStroke.JOIN_ROUND);
		Graphics2D g2 = (Graphics2D) g;
		g2.setStroke(s);
		
		//Strong stroke
		BasicStroke strong = new BasicStroke(4.0f,
				BasicStroke.CAP_ROUND,
				BasicStroke.JOIN_ROUND);	
		

		for(postItPair p: singleLinkMap){
			double Ax = p.a.getX() + (p.a.getSize().width / 2);
			double Ay = p.a.getY() + (p.a.getSize().height / 2);
			double Bx =  p.b.getX() + (p.b.getSize().width / 2);
			double By = p.b.getY() + (p.b.getSize().height / 2);
			
			//Check 
			//int triangularArea = (Ax  * (By - currMeY) + Bx * ((int)currMeY - Ay) + (int)currMeX * (Ay - By));
			//System.out.println("TRIANGULAR AREA  = " + triangularArea);
			//System.out.println("X " + currMeY);
			//System.out.println("Y " + currMeX);
			
			double slope1 = (Ay - By)/(Ax - Bx);
			double slope2 = (Ay - currMeY)/(Ax - currMeX);
			//System.out.println("SLOPE 1 = " + slope1 + " SLOPE 2 = " + slope2);
			
			if(slope1 - slope2 < 0.2 && slope1 - slope2 > -0.2 && 
					((currMeX < Ax && currMeX > Bx) || (currMeX > Ax && currMeX < Bx)) && 
					((currMeY < Ay && currMeY > By) || (currMeY > Ay && currMeY < By))){
				
				//toDelete = p;
				singleLinkMap.remove(p);
				//g2.setStroke(strong);
				//g2.drawLine((int)Ax, (int)Ay, (int)Bx, (int)By);
				
			}
			else{
				g2.setStroke(s);
				g2.drawLine((int)Ax, (int)Ay, (int)Bx, (int)By);
			}
			
			
		}
		for(ArrayList<PostIt> pList : groupLinkMap.keySet()){
			int halfWidth = 0;
			int halfHeight = 0;

			//Determine average point of key
			double avgXK = 0.0;
			double avgYK = 0.0;
			for(PostIt p : pList){
				avgXK = avgXK + p.getX();
				avgYK = avgYK + p.getY();

				halfWidth = p.getWidth() / 2;
				halfHeight = p.getHeight() / 2;
			}
			avgXK = avgXK / pList.size();
			avgYK = avgYK / pList.size();


			//Determine average point of value
			double avgXV = 0.0;
			double avgYV = 0.0;
			for(PostIt p : groupLinkMap.get(pList)){
				avgXV = avgXV + p.getX();
				avgYV = avgYV + p.getY();
			}
			avgXV = avgXV / groupLinkMap.get(pList).size();
			avgYV = avgYV / groupLinkMap.get(pList).size();

			g2.drawLine((int)avgXK + halfWidth, (int)avgYK + halfHeight, 
					(int)avgXV + halfWidth, (int)avgYV + halfHeight);
		}

	}

	//ERIC
	public static void singleLink(PostIt p){

		boolean pairEstablished = false;

		if(singlePostIt != null && !p.getText().equals("") && !singlePostIt.getText().equals("")){
			for(postItPair pair: singleLinkMap){
				if(pair.a == singlePostIt && pair.b == p
						|| pair.a == p && pair.b == singlePostIt){
					pairEstablished = true;

				}
			}

			if(!pairEstablished){
				postItPair pairOut = new postItPair(singlePostIt, p);

				singleLinkMap.add(pairOut);

				singlePostIt = null;
			}
		}
		else{
			singlePostIt = p;
		}
		BoardPane.getInstance().repaint();
	}

	//ERIC
	public static void groupLink(PostIt p){
		System.out.println("DOING A GROUP LINK");

		boolean pairEstablished = false;
		ArrayList<PostIt> listOut = new ArrayList<PostIt>();

		ArrayList<PostIt> pList = new ArrayList<PostIt>();

		//ADD ALL nodes overlapping p to plist
		for(PostIt post: allPostIts){
			//Check for overlap
			if(((post.getX() >= p.getX() && post.getX() <= p.getX() + p.getWidth())
					|| (post.getX() + post.getWidth() >= p.getX() && post.getX() + post.getWidth() <= p.getX() + p.getWidth() ) )
					&& ((post.getY() >= p.getY() && post.getY() <= p.getY() + p.getHeight())
							|| (post.getY() + post.getHeight() >= p.getY() && post.getY() + post.getHeight() <= p.getY() + p.getHeight() ))){
				pList.add(post);
			}

		}

		if(!groupLinkList.isEmpty()){
			System.out.println("FIRST LIST IS NOT EMPTY");

			for(ArrayList<PostIt> pairList: groupLinkMap.keySet()){
				boolean checkOtherSide = true;
				boolean existingPair = false;

				//check keyside of map
				for(PostIt listPost : groupLinkList){
					if(!pairList.contains(listPost)){
						checkOtherSide = false;
					}
				}
				if(checkOtherSide){
					for(PostIt pListPost : pList){
						if(!groupLinkMap.get(pairList).contains(pListPost)){
							checkOtherSide = false;
						}
					}
				}
				//Pair is preestablished
				if(checkOtherSide){
					System.out.println("SHOULD NOT OCCUR 1");
					existingPair = true;
				}


				checkOtherSide = true;


				//check valueside of map
				for(PostIt listPost : groupLinkList){
					if(!groupLinkMap.get(pairList).contains(listPost)){
						checkOtherSide = false;
					}
				}
				if(checkOtherSide){
					for(PostIt pListPost : pList){
						if(!pairList.contains(pListPost)){
							checkOtherSide = false;
						}
					}
				}
				if(checkOtherSide){
					System.out.println("SHOULD NOT OCCUR 2");
					existingPair = true;
				}

				System.out.println(existingPair);
				if(!existingPair){
					System.out.println("PUTTING GROUP PAIR INTO MAP");

					groupLinkMap.put(groupLinkList, pList);


					groupLinkList = new ArrayList<PostIt>();
				}
			}

			if(groupLinkMap.isEmpty()){
				groupLinkMap.put(groupLinkList, pList);


				groupLinkList = new ArrayList<PostIt>();
			}
		}
		else{
			for(PostIt post: allPostIts){
				//Check for overlap
				if(((post.getX() >= p.getX() && post.getX() <= p.getX() + p.getWidth())
						|| (post.getX() + post.getWidth() >= p.getX() && post.getX() + post.getWidth() <= p.getX() + p.getWidth() ) )
						&& ((post.getY() >= p.getY() && post.getY() <= p.getY() + p.getHeight())
								|| (post.getY() + post.getHeight() >= p.getY() && post.getY() + post.getHeight() <= p.getY() + p.getHeight() ))){
					System.out.println("ADDING TO GROUP LINK LIST");
					groupLinkList.add(post);
				}

			}
		}
		
		BoardPane.getInstance().repaint();
	}

	

	
	
	//ERIC 
		private static class postItPair{
			public PostIt a;
			public PostIt b;

			public postItPair(PostIt a, PostIt b){
				this.a = a;
				this.b = b;
			}
			
			public boolean contains(PostIt a){
				return this.a == a || this.b == a;
			}
		}
}